﻿// =============================================================================
// TNTTools ReflectiveProperty.cs
// Copyright (c) 2013 TNTTechnologies Inc.
// =============================================================================

#region

using System;
using System.Collections;
using System.Reflection;
using System.Text;

using TNTTools.Reflection.Interfaces;

#endregion

namespace TNTTools.Reflection{
    public class ReflectiveProperty{
        #region QuickType enum

        public enum QuickType{
            UnDefinedObject,
            Double,
            Int32,
            String,
            DateTime,
            Boolean,
            Byte,
            SByte,
            Char,
            Decimal,
            Single,
            UInt32,
            Int64,
            UInt64,
            Int16,
            UInt16
        };

        #endregion

        public Attributes ClassMarkUpAttributesXml;
        public bool IsLastBasicTypeAttribute = false; //Note: Not properties for performance
        public GetterMethod getter;
        public PropertyInfo info;
        public bool isList = false;
        public SetterMethod setter;
        public QuickType type;

        public ReflectiveProperty(
            PropertyInfo propertyInfo,
            bool isList,
            Attributes classMarkUpAttributesXml,
            GetterMethod getter,
            SetterMethod setter){
            this.info = propertyInfo;
            this.isList = isList;
            this.ClassMarkUpAttributesXml = classMarkUpAttributesXml;
            this.getter = getter;
            this.setter = setter;
            this.InitializeQuickType();
        }

        /// <summary>
        ///     up to 100x faster than reflection's GetValue
        /// </summary>
        /// <param name="reflectiveInfo"></param>
        /// <param name="source"></param>
        /// <returns></returns>
        private void InitializeQuickType(){
            Type propertyType = this.info.PropertyType;

            // If this is nullable get the underlying type
            if (this.info.PropertyType.IsGenericType
                && this.info.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)){
                propertyType = this.info.PropertyType.GetGenericArguments()[0];
            }

            if (propertyType == typeof(Boolean)){
                this.type = QuickType.Boolean;
            } else if (propertyType == typeof(byte)){
                this.type = QuickType.Byte;
            } else if (propertyType == typeof(sbyte)){
                this.type = QuickType.SByte;
            } else if (propertyType == typeof(char)){
                this.type = QuickType.Char;
            } else if (propertyType == typeof(decimal)){
                this.type = QuickType.Decimal;
            } else if (propertyType == typeof(double)){
                this.type = QuickType.Double;
            } else if (propertyType == typeof(Single)){
                this.type = QuickType.Single;
            } else if (propertyType == typeof(Int32)){
                this.type = QuickType.Int32;
            } else if (propertyType == typeof(UInt32)){
                this.type = QuickType.UInt32;
            } else if (propertyType == typeof(Int64)){
                this.type = QuickType.Int64;
            } else if (propertyType == typeof(UInt64)){
                this.type = QuickType.UInt64;
            } else if (propertyType == typeof(Int16)){
                this.type = QuickType.UInt16;
            } else if (propertyType == typeof(Int16)){
                this.type = QuickType.UInt16;
            } else if (propertyType == typeof(Int16)){
                this.type = QuickType.UInt16;
            } else if (propertyType == typeof(string)){
                this.type = QuickType.String;
            } else if (propertyType == typeof(DateTime)){
                this.type = QuickType.DateTime;
            } else{
                // Implement another value type here.
            }
        }

        /// <summary>
        ///     Loads an object with data by setting it's property with corresponding XML Data
        /// </summary>
        /// <param name="PropertyName"></param>
        /// <param name="parentObject"></param>
        public void SetValue(object parentObject, string value){
            this.setter.SetValue(this.type, parentObject, value);
        }

        /// <summary>
        ///     Interact with serializer to organize the order and xml structure
        /// </summary>
        /// <param name="stringStream"></param>
        /// <param name="source"></param>
        /// <param name="serializer"></param>
        public virtual void Write(StringBuilder stringStream, object source, ISimpleSerializer serializer){
            if (this.isList){
                this.WriteList(stringStream, source, serializer);
            } else{
                bool isXMLAttribute = (serializer.GetAttributeTypes().Contains(this.info.PropertyType));
                if (isXMLAttribute){
                    serializer.WriteXmlAttribute(
                        stringStream, this, this.getter.GetStringValue(this.type, source), this.info.Name);
                } else{
                    this.WriteObject(stringStream, source, serializer);
                }
            }
        }

        public virtual void WriteObject(StringBuilder stringStream, object source, ISimpleSerializer serializer){
            object propValue = this.getter.objectGetter(source);
            if (propValue != null){
                // we have a Class/Struct so do a xml element
                serializer.Serialize(stringStream, propValue);
            }
        }

        public virtual void WriteList(StringBuilder stringStream, object source, ISimpleSerializer serializer){
            stringStream.AppendFormat("<{0}>", this.info.Name);
            object propValue = this.getter.objectGetter(source);
            foreach (object item in (IEnumerable)propValue){
                serializer.Serialize(stringStream, item);
            }
            stringStream.AppendFormat("</{0}>", this.info.Name);
        }
    }
}